package com.cwx.mychat.utils.minio;


import com.cwx.mychat.entity.enums.ExceptionBizCodeEnum;
import com.cwx.mychat.exception.BusinessException;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.DeleteObject;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Base64;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;


@Slf4j
@Component
public class MinioUtil {

    @Value("${minio.buketName}")
    private String bucketName;

    @Value("${minio.url}")
    private String url;

    @Autowired
    private MinioClient minioClient;

    /**
     * 上传文件，返回文件下载地址
     * 有目录
     * @param file
     * @return
     * @throws BusinessException
     */
    public String uploadFile(MultipartFile file, String catalogue) throws BusinessException {
        try{
            //判断桶是否存在
            boolean buketExists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(this.bucketName).build());
            if(!buketExists){
                //如果不存在，就创建桶
                minioClient.makeBucket(MakeBucketArgs.builder().bucket(this.bucketName).build());
            }
            //本地时间
            String time = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
            String fileName = catalogue + "/" + time + "/" + file.getOriginalFilename();
            if(catalogue == null){
                fileName = time + "/" + file.getOriginalFilename();
            }
            minioClient.putObject(PutObjectArgs.builder().
                    bucket(bucketName).
                    object(fileName).
                    stream(file.getInputStream(), file.getSize(), -1).
                    contentType(file.getContentType()).
                    build());
            String fileUrl = this.url + "/" + this.bucketName + "/" + fileName;
            return fileUrl;
        } catch (Exception e){
            log.info("上传失败", e);
            throw new BusinessException(ExceptionBizCodeEnum.FILE_UPLOAD_FAILED);
        }
    }

    /**
     * 删除文件
     * @param url
     * @return
     * @throws BusinessException
     */
    public boolean deleteFile(String url) throws BusinessException {

        //根据传过来的url ：http://47.109.191.105:9000/chat/file/2025-05-22/新建 文本文档.txt 获取要删除的文件路径
        try {
            url = parseUrl(url);
            minioClient.removeObject(RemoveObjectArgs.builder().bucket(this.bucketName).object(url).build());
            return true;
        }catch (Exception e){
            log.error("删除文件失败， {}", e.getMessage(), e);
            throw new BusinessException(ExceptionBizCodeEnum.FILE_DELETE_ERROR);
        }
    }

    /**
     * 批量删除文件
     * @param urls
     * @return
     * @throws BusinessException
     */
    public boolean deleteFiles(List<String> urls) throws BusinessException {
        try {
            // 转换为 DeleteObject 列表,排除掉为空的值
            List<DeleteObject> urlList = urls.stream().filter(StringUtils::isNotBlank).map(url -> new DeleteObject(parseUrl(url))).collect(Collectors.toList());
            minioClient.removeObjects(RemoveObjectsArgs.builder().bucket(this.bucketName).objects(urlList).build());
            return true;
        }catch (Exception e){
            log.error("批量删除文件失败，{}", e.getMessage(), e);
            return false;
        }
    }


    //解析路径提取关键的路径
    private String parseUrl(String fileUrl) {
        //截取第一个字符串第一个与桶匹配的字段的后面部分
        String[] split = fileUrl.split("/" + this.bucketName + "/");
        return split[1];
    }

    /**
     * 上传文件， 无目录
     * @param file
     * @return
     */
    @SneakyThrows(Exception.class)
    public  String uploadFile(MultipartFile file){
        return uploadFile(file, null);
    }

    /**
     * 根据普通文件，上传文件
     * @param file
     * @param catalogue
     * @return
     * @throws BusinessException
     */
    public String uploadFile(File file, String catalogue) throws BusinessException {
         MultipartFile multipartFile = new FileToMultipartFile(file);
         String s = uploadFile(multipartFile, catalogue);
         return s;
    }

    /**
     * 获取预览url 默认7天
     * @return
     */
    public String previewFile(String fileUrl) {
        URL url = null;
        try {
            url = new URL(fileUrl);
        } catch (MalformedURLException e) {
            log.error("url地址不正确, {}", e.getMessage(), e);
            throw new RuntimeException("url地址不正确！", e);
        }
        String pathName = url.getPath();
        String fileName = pathName.replace("/" +bucketName + "/", "");
        GetPresignedObjectUrlArgs urlArgs = GetPresignedObjectUrlArgs
                .builder()
                .method(Method.GET).bucket(this.bucketName).object(fileName)
                .expiry(7, TimeUnit.DAYS)
                .build();

        try {
            return minioClient.getPresignedObjectUrl(urlArgs);
        } catch (Exception e){
            log.error("获取文件URL失败， {}", e.getMessage(), e);
            throw new RuntimeException("获取文件失败Url！", e);
        }
    }

    /**
     * 下载文件
     * @param originalName 文件名
     * @return
     */
    public InputStream downloadFile(String originalName){
        try{
            return minioClient.getObject(GetObjectArgs.builder()
                    .bucket(this.bucketName)
                    .object(originalName).build());
        }catch (Exception e){
            throw new RuntimeException("minio下载文件失败！", e);
        }
    }

    /**
     * 检查文件是否存在
     * @param originalName
     * @return
     */
    public ObjectStat checkFileExits(String originalName){
        try{
            return minioClient.statObject(
                    StatObjectArgs.builder()
                            .bucket(this.bucketName)
                            .object(originalName).build());
        }catch (Exception e){
            log.error("minio检查文件失败！", e);
            throw new RuntimeException("minio检查文件失败！", e);
        }
    }

    /**
     * 创建桶
     */
    public void createBucketName() {
        try {
            if (StringUtils.isBlank(bucketName)) {
                return;
            }
            boolean isExist = minioClient.bucketExists(BucketExistsArgs.builder().bucket(this.bucketName).build());
            if (isExist) {
                log.info("桶 {} 已经存在！", bucketName);
            } else {
                minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.error(e.getMessage());
        }
    }

    /**
     * 获取默认桶策略
     * Statement: 策略授权语句，描述策略的详细信息，包含Effect（效果）、Action（动作）、Principal（用户）、Resource（资源）和Condition（条件）。其中Condition为可选
     * Effect:	Effect（效果）作用包含两种：Allow（允许）和Deny（拒绝），系统预置策略仅包含允许的授权语句，自定义策略中可以同时包含允许和拒绝的授权语句，当策略中既有允许又有拒绝的授权语句时，遵循Deny优先的原则。
     * Action	Action（动作）对资源的具体操作权限，格式为：服务名:资源类型:操作，支持单个或多个操作权限，支持通配符号*，通配符号表示所有。例如 s3:GetObject ，表示获取对象
     * Resource	Resource（资源）策略所作用的资源，支持通配符号*，通配符号表示所有。在JSON视图中，不带Resource表示对所有资源生效。Resource支持以下字符：-_0-9a-zA-Z*./\
     *          ，如果Resource中包含不支持的字符，请采用通配符号*。例如：arn:aws:s3:::my-bucketname/myobject*\，表示minio中my-bucketname/myobject目录下所有对象文件。
     * Condition  Condition（条件）您可以在创建自定义策略时，通过Condition元素来控制策略何时生效。Condition包括条件键和运算符，条件键表示策略语句的Condition元素，分为全局级条件键和服务级条件键。全局级条件键（前缀为g:）适用于所有操作，
     *          服务级条件键（前缀为服务缩写，如obs:）仅适用于对应服务的操作。运算符与条件键一起使用，构成完整的条件判断语句。
     * @return
     */
    private StringBuilder defaultBucketPolicy(){
        StringBuilder builder=new StringBuilder();
        builder.append("{\n" +
                "  \"Version\": \"2012-10-17\",\n" +
                "  \"Statement\": [\n" +
                "    {\n" +
                "      \"Effect\": \"Allow\",\n" +
                "      \"Action\": [\n" +
                "                \"s3:ListAllMyBuckets\",\n" +
                "                \"s3:ListBucket\",\n" +
                "                \"s3:GetBucketLocation\",\n" +
                "                \"s3:GetObject\",\n" +
                "                \"s3:PutObject\",\n" +
                "                \"s3:DeleteObject\"\n" +
                "      ],\n" +
                "      \"Principal\":\"*\",\n" +
                "      \"Resource\": [\n" +
                "        \"arn:aws:s3:::"+this.bucketName+"/*\"\n" +
                "      ]\n" +
                "    }\n" +
                "  ]\n" +
                "}");
        return builder;
    }
    /**
     * 设置桶策略
     * @param builder 策略json
     */
    @SneakyThrows(Exception.class)
    public void createBucketPolicy(StringBuilder builder) {
        if(builder.length()==0) builder=defaultBucketPolicy();
        minioClient.setBucketPolicy(SetBucketPolicyArgs.builder().bucket(bucketName).config(builder.toString()).build());
    }
    /**
     * 查看桶策略
     * @return
     */
    @SneakyThrows(Exception.class)
    public String queryBucketPolicy() {
        String bucketPolicy = minioClient.getBucketPolicy(GetBucketPolicyArgs.builder().bucket(bucketName).build());
        log.info(bucketPolicy);
        return bucketPolicy;
    }

    /**
     * base64图片上传
     * @param imageBase64
     * @param imageName
     * @return
     */
    public ObjectWriteResponse uploadImage( String imageBase64, String imageName) {
        if (!StringUtils.isEmpty(imageBase64)) {
            Date date = new Date();
            InputStream in = base64ToInputStream(imageBase64);
            Calendar calendar = Calendar.getInstance();
            calendar.setTime(date);
            String newName = System.currentTimeMillis() + "_" + imageName + ".jpg";
            String year = String.valueOf(calendar.get(Calendar.YEAR));
            String month = String.valueOf(calendar.get(Calendar.MONTH));
            return uploadFile(year + "/" + month + "/" + newName, in);

        }
        return null;
    }

    /**
     * 通过文件流上传文件
     * @param objectName
     * @param inputStream
     * @return
     */
    @SneakyThrows(Exception.class)
    public ObjectWriteResponse uploadFile(String objectName, InputStream inputStream){
        return minioClient.putObject(
                PutObjectArgs.builder()
                        .bucket(bucketName)
                        .object(objectName)
                        .stream(inputStream, inputStream.available(), -1)
                        .build());
    }

    public static InputStream base64ToInputStream(String base64) {
        ByteArrayInputStream stream = null;
        try {
            byte[] bytes = Base64.getDecoder().decode(base64.trim());
            stream = new ByteArrayInputStream(bytes);
        } catch (IllegalArgumentException e) {
            e.printStackTrace(); // 处理非法 Base64 字符串
        }
        return stream;
    }

}
